Skip to content

Route subagents on the spawn message when plaintext at PreToolUse - #266

Merged
masonc08 merged 1 commit into
mainfrom
masonc08/route-subagent-on-message
Aug 5, 2026
Merged

Route subagents on the spawn message when plaintext at PreToolUse#266
masonc08 merged 1 commit into
mainfrom
masonc08/route-subagent-on-message

Conversation

@masonc08

@masonc08 masonc08 commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

What & why

Codex subagent smart routing was effectively inert: the routing hook read task_name (a path label like task_3) or fell back to the generic "Codex subagent task" string, giving the task_v1 router no task signal — so it always returned the default arm (gpt-5-6-sol).

The actual task content lives in tool_input.message, which is plaintext at PreToolUse time (Codex encrypts it at send-time, but the hook fires before that). This was confirmed empirically by capturing a real spawn payload:

"tool_input": {
    "fork_context": true,
    "message": "Explore this codebase specifically for parser-related code. Identify the parser implementation(s)..."
}

The fix

route_spawn_tool now reads message first (when it's a plaintext string), falling back to task_name / agent_name (weaker labels) then the generic default. The isinstance(str) filter means encrypted dicts (if they ever appear) skip cleanly — no regression.

task = next(
    (
        value
        for field in ("message", "task_name", "agent_name")
        if isinstance(value := tool_input.get(field), str) and value
    ),
    default_task_label,
)

How do you know it works

  • New tests: test_spawn_routes_on_plaintext_message_when_present (verifies the router receives the real message text), test_spawn_falls_through_encrypted_message_to_task_name (verifies encrypted dicts fall through, no regression).
  • Full suite: 1107 passed, lint clean.
  • Empirically confirmed: a real Codex subagent spawn produced a 422-char plaintext message in the short length bucket (400–1200) — exactly where task_v1 can differentiate between arms, instead of the contentless fallback that always returned the default.

This is a follow-up to #251 (codex smart routing, merged) and #255 (claude smart routing, merged).

This pull request and its description were written by Isaac.

The subagent-routing hook now reads `tool_input.message` (the actual task
content) as the routing task when it's a plaintext string at PreToolUse time.
Codex encrypts the message at send-time, but the hook fires before that — so
if the message is readable here, the router gets real task signal instead of
the generic "Codex subagent task" fallback (which always returned the default
arm).

When `message` is an encrypted dict (or absent), routing falls through to
`task_name` / `agent_name` as before — no regression. The field-selection logic
uses `isinstance(str)` so non-string values (encrypted dicts) are skipped
cleanly.

Co-authored-by: Isaac
@masonc08
masonc08 marked this pull request as ready for review August 5, 2026 01:11
@masonc08
masonc08 merged commit ecb14e7 into main Aug 5, 2026
1 of 2 checks passed
@masonc08
masonc08 deleted the masonc08/route-subagent-on-message branch August 5, 2026 02:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant